package com.aphrodite.io;

import java.io.IOException;
import java.io.Reader;

/**
 * A character stream whose source is a string.
 * 
 * 
 * @author kentelt
 * 
 */
public class StringReader extends Reader {
	private int length;
	private int mark;
	private int next;
	private String str;

	/**
	 * Create a new String reader
	 * 
	 * @param s
	 *            String providing the character stream
	 */
	public StringReader(String s) {
		next = 0;
		mark = 0;
		str = s;
		length = s.length();
	}

	public void close() {
		str = null;
	}

	private void ensureOpen() throws IOException {
		if (str == null)
			throw new IOException("Stream closed");
		else
			return;
	}

	/**
	 * Mark the present position in the stream. Subsequent calls to reset() will
	 * reposition the stream to this point.
	 * 
	 * @param readAheadLimit
	 *            Limit on the number of characters that may be read while still
	 *            preserving the mark. Because the stream's input comes from a
	 *            string, there is no actual limit, so this argument must not be
	 *            negative, but is otherwise ignored.
	 * @throws IOException
	 *             If an I/O error occurs
	 * @throws IllegalArgumentException
	 *             If readAheadLimit is < 0
	 */
	public void mark(int readAheadLimit) throws IOException {
		if (readAheadLimit < 0)
			throw new IllegalArgumentException("Read-ahead limit < 0");
		synchronized (lock) {
			ensureOpen();
			mark = next;
		}
	}

	/**
	 * Tell whether this stream supports the mark() operation, which it does.
	 * 
	 * @return true if and only if this stream supports the mark operation.
	 */
	public boolean markSupported() {
		return true;
	}

	/**
	 * Read a single character.
	 * 
	 * @return The character read, or -1 if the end of the stream has been
	 *         reached.
	 * @throws IOException
	 *             If an I/O error occurs
	 */
	public int read() throws IOException {
		ensureOpen();
		if (next >= length)
			return -1;
		return str.charAt(next++);

	}

	/**
	 * Read characters into a portion of an array.
	 * 
	 * @param cbuf
	 *            Destination buffer
	 * 
	 * @param offset
	 *            Offset at which to start writing characters
	 * 
	 * @param len
	 *            Maximum number of characters to read
	 * 
	 * @return The number of characters read, or -1 if the end of the stream has
	 *         been reached
	 * @throws IOException
	 *             If an I/O error occurs
	 */
	public int read(char[] cbuf, int offset, int len) throws IOException {
		ensureOpen();
		if (offset < 0 || offset > cbuf.length || len < 0 || offset + len > cbuf.length || offset + len < 0)
			throw new IndexOutOfBoundsException();
		if (len == 0)
			return 0;
		if (next < length) {
			int k;
			k = Math.min(length - next, len);
			str.getChars(next, next + k, cbuf, offset);
			next += k;
			return k;
		} else {
			return -1;
		}
	}

	/**
	 * Tell whether this stream is ready to be read.
	 * 
	 * @return True if the next read() is guaranteed not to block for input
	 * 
	 * @throws IOException
	 */
	public boolean ready() throws IOException {
		ensureOpen();
		return true;

	}

	/**
	 * Reset the stream to the most recent mark, or to the beginning of the
	 * string if it has never been marked.
	 * 
	 * @throws IOException
	 *             If an I/O error occurs
	 */
	public void reset() throws IOException {
		synchronized (lock) {
			ensureOpen();
			next = mark;
		}
	}

	/**
	 * Skips the specified number of characters in the stream.
	 * 
	 * @param nsteps
	 *            The number of characters to skip
	 * @return The number of characters actually skipped
	 */
	public long skip(long nsteps) throws IOException {

		ensureOpen();
		if (next >= length)
			return 0L;
		long l1;
		l1 = Math.min(length - next, nsteps);
		l1 = Math.max(-next, l1);
		next += l1;
		return l1;
		// obj;
		// JVM INSTR monitorexit ;
		// return;
		// Exception exception;
		// exception;
		// throw exception;
	}
}
